package com.eobard.obfuscate.core;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.eobard.config.obfuscate.ObfuscatorConfig;
import com.eobard.obfuscate.IObfuscator;
import com.eobard.params.ClassRandomShuffleParams;
import com.eobard.utils.CollectionUtils;
import com.eobard.utils.ExceptionUtils;
import com.eobard.utils.FileUtils;
import com.eobard.utils.ObfuscatorUtils;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * @Author: Eobard Gu
 * @Date: 2025-09-05
 * @Description: 类随机打乱混淆器,该功能会将basePackage下所有包及其子包的所有Java文件的所在位置进行打乱，每个文件会被随机移动到basePackage层级中的任意包下
 */
@Component
@Slf4j
public class ClassRandomShuffleObfuscator implements IObfuscator {
    @Value("${obfuscate.enable.typeSolver.logging:false}")
    private boolean print;

    private final Random random = new Random();

    //代码根路径
    private static String sourceRoot;

    //基础父包名
    private static String basePackage;

    //排除不想打乱的包名集合
    private static Set<String> excludePackages;

    //类所属包映射表,k为旧包名.类名,v为新包名.类名
    private static Map<String, String> classMapping = new HashMap<>();

    //数据初始化代码
    @Override
    public void initialize(ObfuscatorConfig config) {
        //清除上一次的记录
        excludePackages = null;
        CollectionUtils.clear(classMapping);

        ClassRandomShuffleParams params = config.getClassRandomShuffleParams();

        sourceRoot = config.getSourceRoot();
        basePackage = params.getBasePackage();
        //检查非空
        ExceptionUtils.checkNonNull(
                List.of(sourceRoot, basePackage),
                List.of("sourceRoot不能为空", "basePackage不能为空")
        );

        if (CollectionUtil.isNotEmpty(params.getExcludePackages())) {
            excludePackages = params.getExcludePackages();
            //检查排除的包是否存在
            FileUtils.checkDirectoryExist(excludePackages, sourceRoot);
        }
        //规范化排除的包
        excludePackages = ObfuscatorUtils.normalizedPackage(excludePackages);
    }

    //核心混淆代码
    @Override
    public void obfuscate(ObfuscatorConfig config) {
        //获取基础包所在的完整路径
        Path baseDir = Paths.get(config.getSourceRoot()).resolve(basePackage.replace(".", File.separator));
        if (!Files.exists(baseDir) || !Files.isDirectory(baseDir)) {
            throw new RuntimeException("基础包路径不存在:" + baseDir);
        }

        try {
            //收集baseDir下所有的Java文件,如果有指定包则排除
            List<Path> allJavas = Files.walk(baseDir)
                    //筛选出Java文件
                    .filter(path -> path.toString().endsWith(".java"))
                    //如果baseDir下有Java文件则不动它,只动baseDir下包里面的Java文件
                    .filter(path -> {
                        int index = path.toString().lastIndexOf(File.separator);
                        //截取Java文件的父路径,eg:/Users/eobardgu/Test/src/main/java/com/eobard/A.java -> /Users/eobardgu/Test/src/main/java/com/eobard
                        String parentUrl = path.toString().substring(0, index);
                        return !parentUrl.equals(baseDir.toString());
                    })
                    //筛选出没有在excludePackages的包文件夹
                    .filter(path -> {
                        String rel = baseDir.relativize(path.getParent()).toString().replace(File.separatorChar, '.');
                        String currentPkg = rel.isEmpty() ? basePackage : basePackage + "." + rel;
                        return excludePackages.stream().noneMatch(ex -> currentPkg.equals(ex) || currentPkg.startsWith(ex));
                    })
                    .toList();

            //收集baseDir下的所有包名,如果有指定包则排除
            List<String> allPackages = Files.walk(baseDir)
                    //筛选出文件夹
                    .filter(Files::isDirectory)
                    //排除掉自身baseDir文件夹
                    .filter(dir -> !dir.equals(baseDir))
                    .map(dir -> {
                        //获取当前包相对于baseDir的路径
                        String rel = baseDir.relativize(dir).toString().replace(File.separatorChar, '.');
                        return rel.isEmpty() ? basePackage : basePackage + "." + rel;
                    })
                    //筛选出没有在excludePackages的包文件夹
                    .filter(pkg -> excludePackages.stream().noneMatch(ex -> pkg.equals(ex) || pkg.startsWith(ex)))
                    .toList();

            List<Path> shuffled = new ArrayList<>(allJavas);
            Collections.shuffle(shuffled);

            //遍历文件
            for (Path file : shuffled) {
                //解析每个Java文件
                CompilationUnit cu = StaticJavaParser.parse(file);
                //获取当前Java文件的类名
                String className = ObfuscatorUtils.getClassName(cu);
                //获取当前Java文件的完整类限定名
                String oldFullyQualifiedName = ObfuscatorUtils.getClassName(cu, true);

                //随机从allPackages中取一个包
                String newPKg = allPackages.get(random.nextInt(allPackages.size()));

                //构成当前Java文件新的完整类限定名
                String newFullyQualifiedName = newPKg + "." + className;

                //如果旧的类限定名和新的类限定名不一致,那么就移动文件
                if (!oldFullyQualifiedName.equals(newFullyQualifiedName)) {
                    //生成新的路径
                    Path newPath = Paths.get(sourceRoot).resolve(newPKg.replace('.', File.separatorChar)).resolve(className + ".java");

                    //如果新的路径下面有同名的Java文件,那么就跳过这次移动
                    if (Files.exists(newPath)) {
                        if (print)
                            log.error("skip【{}.java】to:【{}】,because【{}.java】already has same class", oldFullyQualifiedName, newPKg, newFullyQualifiedName);
                        continue;
                    }
                    //修改package声明
                    cu.setPackageDeclaration(newPKg);

                    //移动文件
                    Files.createDirectories(newPath.getParent());
                    Files.write(newPath, cu.toString().getBytes(StandardCharsets.UTF_8));
                    Files.deleteIfExists(file);

                    //移动成功后,放入类所属包映射表
                    classMapping.put(oldFullyQualifiedName, newFullyQualifiedName);
                    if (print)
                        log.info("shuffle {}", String.format("【%-35s】to 【%-35s】", oldFullyQualifiedName + ".java", newFullyQualifiedName + ".java"));
                }
            }

            //更新import
            FileUtils.loopFiles(sourceRoot, path -> {
                try {
                    //解析Java文件
                    CompilationUnit cu = StaticJavaParser.parse(path);

                    //对所有的import替换
                    cu.findAll(ImportDeclaration.class).forEach(imp -> {
                        //获取导入的包名
                        String name = imp.getNameAsString();
                        //默认为类的部分
                        String classPart = name;
                        //默认没有静态导入变量的部分
                        String memberPart = null;

                        int lastDot = name.lastIndexOf('.');
                        //如果是静态导入
                        if (imp.isStatic() && lastDot > 0) {
                            //更新为最新的类全限定名
                            classPart = name.substring(0, lastDot);
                            //剩下的就是变量名部分
                            memberPart = name.substring(lastDot + 1);
                        }

                        //如果类所属包映射表存在,那么就替换导包
                        if (classMapping.containsKey(classPart)) {
                            String newClassPart = classMapping.get(classPart);
                            if (StrUtil.isBlank(memberPart)) {
                                imp.setName(newClassPart);
                            } else {
                                imp.setName(newClassPart + "." + memberPart);
                            }
                        }
                    });

                    //写出文件
                    Files.write(path, cu.toString().getBytes(StandardCharsets.UTF_8));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //是否启动这个功能
    @Override
    public boolean supports(ObfuscatorConfig config) {
        return ObjectUtil.isNotEmpty(config.getClassRandomShuffleParams());
    }
}
